#include "frustum.h"

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <windows.h>
#define GLUT_DISABLE_ATEXIT_HACK
#include <GL/glut.h>
#endif

using namespace krryn::math;
using namespace krryn::view;

frustum::frustum(){
	calculate();
}

void frustum::recalculate(){
	calculate();
}

bool frustum::contains(const vector3f &a_Vector) const{
	for(int i = 0; i < sides_count; i++){
		if(!m_Planes[i].point_in_plane(a_Vector))
			return false;
	}

	return true;
}

bool frustum::contains(const aabb &a_Aabb) const{
	vector3f l_Center = a_Aabb.center();
	vector3f l_Size = a_Aabb.size() * .5f;
 
	for(int i = 0; i < sides_count; i++){
		for(int j = 0; j < 8; j++){
			if(m_Planes[i].point_in_plane(combine(j, l_Center, l_Size))){
				// C++ doen't have something that resembles the 
				// "continue 2" syntax like PHP does so we simulate it
				// with a simple goto
				goto next;
			}
		}

		return false;
next:
		/* do nothing */
		;
	}

	return true;
}

void frustum::calculate(){
	float l_Projection[16];
	float l_View[16];
	float l_Clip[16];

	glGetFloatv(GL_PROJECTION_MATRIX, l_Projection);
	glGetFloatv(GL_MODELVIEW_MATRIX, l_View);

	l_Clip[ 0] = 
		  l_View[ 0] * l_Projection[ 0] 
		+ l_View[ 1] * l_Projection[ 4] 
		+ l_View[ 2] * l_Projection[ 8] 
		+ l_View[ 3] * l_Projection[12];

	l_Clip[ 1] = 
		  l_View[ 0] * l_Projection[ 1] 
		+ l_View[ 1] * l_Projection[ 5] 
		+ l_View[ 2] * l_Projection[ 9] 
		+ l_View[ 3] * l_Projection[13];

	l_Clip[ 2] = 
		  l_View[ 0] * l_Projection[ 2] 
		+ l_View[ 1] * l_Projection[ 6] 
		+ l_View[ 2] * l_Projection[10] 
		+ l_View[ 3] * l_Projection[14];

	l_Clip[ 3] = 
		  l_View[ 0] * l_Projection[ 3] 
		+ l_View[ 1] * l_Projection[ 7] 
		+ l_View[ 2] * l_Projection[11] 
		+ l_View[ 3] * l_Projection[15];

	l_Clip[ 4] = 
		  l_View[ 4] * l_Projection[ 0] 
		+ l_View[ 5] * l_Projection[ 4] 
		+ l_View[ 6] * l_Projection[ 8] 
		+ l_View[ 7] * l_Projection[12];

	l_Clip[ 5] = 
		  l_View[ 4] * l_Projection[ 1] 
		+ l_View[ 5] * l_Projection[ 5] 
		+ l_View[ 6] * l_Projection[ 9] 
		+ l_View[ 7] * l_Projection[13];

	l_Clip[ 6] = 
		  l_View[ 4] * l_Projection[ 2] 
		+ l_View[ 5] * l_Projection[ 6] 
		+ l_View[ 6] * l_Projection[10] 
		+ l_View[ 7] * l_Projection[14];

	l_Clip[ 7] = 
		  l_View[ 4] * l_Projection[ 3] 
		+ l_View[ 5] * l_Projection[ 7] 
		+ l_View[ 6] * l_Projection[11] 
		+ l_View[ 7] * l_Projection[15];

	l_Clip[ 8] = 
		  l_View[ 8] * l_Projection[ 0] 
		+ l_View[ 9] * l_Projection[ 4] 
		+ l_View[10] * l_Projection[ 8] 
		+ l_View[11] * l_Projection[12];

	l_Clip[ 9] = 
		  l_View[ 8] * l_Projection[ 1] 
		+ l_View[ 9] * l_Projection[ 5] 
		+ l_View[10] * l_Projection[ 9] 
		+ l_View[11] * l_Projection[13];

	l_Clip[10] = 
		  l_View[ 8] * l_Projection[ 2] 
		+ l_View[ 9] * l_Projection[ 6] 
		+ l_View[10] * l_Projection[10] 
		+ l_View[11] * l_Projection[14];

	l_Clip[11] = 
		  l_View[ 8] * l_Projection[ 3] 
		+ l_View[ 9] * l_Projection[ 7] 
		+ l_View[10] * l_Projection[11] 
		+ l_View[11] * l_Projection[15];

	l_Clip[12] = 
		  l_View[12] * l_Projection[ 0] 
		+ l_View[13] * l_Projection[ 4] 
		+ l_View[14] * l_Projection[ 8] 
		+ l_View[15] * l_Projection[12];

	l_Clip[13] = 
		  l_View[12] * l_Projection[ 1] 
		+ l_View[13] * l_Projection[ 5] 
		+ l_View[14] * l_Projection[ 9] 
		+ l_View[15] * l_Projection[13];

	l_Clip[14] = 
		  l_View[12] * l_Projection[ 2] 
		+ l_View[13] * l_Projection[ 6] 
		+ l_View[14] * l_Projection[10] 
		+ l_View[15] * l_Projection[14];

	l_Clip[15] = 
		  l_View[12] * l_Projection[ 3] 
		+ l_View[13] * l_Projection[ 7] 
		+ l_View[14] * l_Projection[11] 
		+ l_View[15] * l_Projection[15];

	m_Planes[right].m_Normal = vector3f(
		l_Clip[ 3] - l_Clip[ 0],
		l_Clip[ 7] - l_Clip[ 4], 
		l_Clip[11] - l_Clip[ 8]
	);

	m_Planes[right].m_Distance = l_Clip[15] - l_Clip[12];

	m_Planes[left].m_Normal = vector3f(
		l_Clip[ 3] + l_Clip[ 0],
		l_Clip[ 7] + l_Clip[ 4],
		l_Clip[11] + l_Clip[ 8]
	);
	m_Planes[left].m_Distance = l_Clip[15] + l_Clip[12];

	m_Planes[bottom].m_Normal = vector3f(
		l_Clip[ 3] + l_Clip[ 1],
		l_Clip[ 7] + l_Clip[ 5],
		l_Clip[11] + l_Clip[ 9]
	);
	m_Planes[bottom].m_Distance = l_Clip[15] + l_Clip[13];

	m_Planes[top].m_Normal = vector3f(
		l_Clip[ 3] - l_Clip[ 1],
		l_Clip[ 7] - l_Clip[ 5],
		l_Clip[11] - l_Clip[ 9]
	);
	m_Planes[top].m_Distance = l_Clip[15] - l_Clip[13];

	m_Planes[back].m_Normal = vector3f(
		l_Clip[ 3] - l_Clip[ 2],
		l_Clip[ 7] - l_Clip[ 6],
		l_Clip[11] - l_Clip[10]
	);
	m_Planes[back].m_Distance = l_Clip[15] - l_Clip[14];

	m_Planes[front].m_Normal = vector3f(
		l_Clip[ 3] + l_Clip[ 2],
		l_Clip[ 7] + l_Clip[ 6],
		l_Clip[11] + l_Clip[10]
	);
	m_Planes[front].m_Distance = l_Clip[15] + l_Clip[14];

	for(int i = 0; i < sides_count; i++){
		m_Planes[i].normalize();
	}
}

vector3f frustum::combine(int i, const vector3f &a_Center, const vector3f &l_Size) const{
	int l_X = (i >> 0) & 1 ? -1 : +1;
	int l_Y = (i >> 1) & 1 ? -1 : +1;
	int l_Z = (i >> 2) & 1 ? -1 : +1;

	return vector3f(
		a_Center.m_X + l_X * l_Size.m_X,
		a_Center.m_Y + l_Y * l_Size.m_Y,
		a_Center.m_Z + l_Z * l_Size.m_Z
	);
}
